/* 
  Copyright (c) SilverLining 2015
  Date:    2016-1-7
  File:    text_buffer.cpp  
  Description: 

*/

#include "stdafx.h"

using namespace m;
using namespace m::io;
using namespace m::error;

namespace m
{
	namespace text
	{
		const char Utf16Bom[] = { (char)0xFF, (char)0xFE, (char)0x00 };
		const char Utf8Bom[] = { (char)0xEF, (char)0xBB, (char)0xBF, (char)0x00 };

		struct EncodingData
		{
			TextEncoding Type;
			const char* Bom;
			size_t BomSize;
			DWORD Codepage;
			DWORD WcToMbFlags;
			DWORD MbToWcFlags;
		};

		static const EncodingData Encodings[] =
		{
			{ TextEncodingAscii, nullptr, 0, CP_ACP, 0, 0 },
			{ TextEncodingUtf8, Utf8Bom, strlen(Utf8Bom), CP_UTF8, 0, 0 },
			{ TextEncodingUtf16, Utf16Bom, strlen(Utf16Bom), 0, 0, 0 },
			{ TextEncodingInvalid, nullptr, 0, 0, 0, 0 },
		};

		m::text::text_buffer::text_buffer()
			: _TextBuffer(nullptr)
		{
			assign_new(_TextBuffer, new file_buffer());
		}

		m::text::text_buffer::~text_buffer()
		{
			clear(_TextBuffer);
		}

		TextEncoding m::text::text_buffer::load(io::file_buffer * state)
		{
			assert(state);
			if (state)
			{
				state->set_offset(0);

				TextEncoding encoding = Encoding(state);
				EncodingData data = Encodings[encoding];
				size_t InputSz = state->size();
				assert(InputSz >= data.BomSize);
				if (InputSz >= data.BomSize)
				{
					InputSz -= data.BomSize;
				}
				state->set_offset(data.BomSize);

				char* input = new char[InputSz];
				size_t read = state->read(input, InputSz);
				ptrdiff_t OutputSz;

				if (data.Type != TextEncoding::TextEncodingUtf16)
				{
					if (read > INT32_MAX)
					{
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_BUFFER_OVERFLOW), "Buffer too large.");
					}

					OutputSz = MultiByteToWideChar(
						data.Codepage,
						data.MbToWcFlags,
						input,
						(int)read,
						NULL,
						0
					);
					if (OutputSz == 0 && InputSz != 0)
					{
						delete[] input;
						DBGW(GetLastError(), L"Failed to get text data.");
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_BAD_FORMAT), "Invalid text.");
					}
				}

				_TextBuffer->set_offset(0);
				_TextBuffer->set_size(0);

				if (data.Type == TextEncoding::TextEncodingUtf16)
				{
					_TextBuffer->write((const char*)input, read);
				}
				else
				{
					wchar_t* text = new wchar_t[OutputSz];

					OutputSz = MultiByteToWideChar(
						data.Codepage,
						data.MbToWcFlags,
						input,
						(int)read,
						text,
						(int)OutputSz
					);
					if (OutputSz == 0 && InputSz != 0)
					{
						delete[] input;
						delete[] text;
						DBGW(GetLastError(), L"Failed to get text data.");
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_BAD_FORMAT), "Invalid text.");
					}

					_TextBuffer->write((const char*)text, OutputSz * sizeof(wchar_t));
					delete[] text;
				}

				_TextBuffer->set_offset(0);
				delete[] input;

				return encoding;
			}
			return TextEncoding::TextEncodingInvalid;
		}

		void text_buffer::save(io::file_buffer * state, TextEncoding encoding)
		{
			assert(state);
			if (state)
			{
				if (encoding == TextEncoding::TextEncodingInvalid)
				{
					throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_BAD_FORMAT), "Invalid text encoding.");
				}

				size_t OutputSz;
				EncodingData data = Encodings[encoding];
				size_t cchInput = size();
				size_t InputSz = sizeof(wchar_t) * cchInput;

				size_t oldOffset = offset();
				set_offset(0);
				wchar_t* text = new wchar_t[InputSz];
				InputSz = _TextBuffer->read((char*)text, InputSz);
				set_offset(oldOffset);

				if (data.Type != TextEncoding::TextEncodingUtf16)
				{
					if (cchInput > INT32_MAX)
					{
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_BUFFER_OVERFLOW), "Buffer too large.");
					}

					OutputSz = WideCharToMultiByte(
						data.Codepage,
						data.WcToMbFlags,
						text,
						(int)cchInput,
						NULL,
						0,
						NULL,
						NULL
						);

					if (OutputSz == 0 && InputSz != 0)
					{
						delete[] text;
						DBGW(GetLastError(), L"Failed to get text data.");
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_BAD_FORMAT), "Invalid text.");
					}
				}

				char* output;

				if (data.Type == TextEncoding::TextEncodingUtf16)
				{
					OutputSz = InputSz;
					output = (char*)text;
				}
				else
				{
					output = new char[OutputSz];

					OutputSz = WideCharToMultiByte(
						data.Codepage,
						data.WcToMbFlags,
						text,
						(int)cchInput,
						output,
						(int)OutputSz,
						NULL,
						NULL
						);

					if (OutputSz == 0 && InputSz != 0)
					{
						delete[] output;
						delete[] text;
						DBGW(GetLastError(), L"Failed to get text data.");
						throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_BAD_FORMAT), "Invalid text.");
					}
				}

				state->set_offset(0);
				state->set_size(0);
				if (data.BomSize > 0)
				{
					state->write(data.Bom, data.BomSize);
				}
				state->write(output, OutputSz);
				state->set_offset(0);
				if (data.Type != TextEncoding::TextEncodingUtf16)
				{
					delete[] output;
				}
				delete[] text;
			}
		}

		size_t text_buffer::size() const
		{
			return _TextBuffer->size() / sizeof(wchar_t);
		}
		
		void text_buffer::set_size(const size_t & Size)
		{
			_TextBuffer->set_size(Size * sizeof(wchar_t));
		}

		ptrdiff_t text_buffer::offset() const
		{
			return _TextBuffer->offset() / sizeof(wchar_t);
		}

		void text_buffer::set_offset(const ptrdiff_t & off)
		{
			_TextBuffer->set_offset(off * sizeof(wchar_t));
		}

		wchar_t text_buffer::read()
		{
			size_t toRead = sizeof(wchar_t);
			wchar_t ret;
			size_t sz = _TextBuffer->read((char*)&ret, toRead);
			if (sz != toRead)
			{
				return 0;
			}
			return ret;
		}
		
		size_t text_buffer::read(wchar_t * Buffer, const size_t & BufferSize)
		{
			assert(Buffer);
			if (Buffer)
			{
				if (BufferSize > 0)
				{
					size_t toRead = (BufferSize - 1) * sizeof(wchar_t);
					size_t sz = _TextBuffer->read((char*)Buffer, toRead);
					sz /= sizeof(wchar_t);
					Buffer[sz] = 0;
					return sz;
				}
			}
			return 0;
		}

		void text_buffer::write(wchar_t ch)
		{
			size_t toWrite = sizeof(wchar_t);
			_TextBuffer->write((const char*)&ch, toWrite);
		}

		void m::text::text_buffer::write(const wchar_t * Buffer)
		{
			assert(Buffer);
			if (Buffer)
			{
				size_t toWrite = wcslen(Buffer) * sizeof(wchar_t);
				_TextBuffer->write((const char*)Buffer, toWrite);
			}
		}

		TextEncoding text_buffer::Encoding(io::file_buffer * state) const
		{
			TextEncoding encoding = TextEncoding::TextEncodingInvalid;
			size_t oldOff = state->offset();
			state->set_offset(0);

			size_t BufSz = 0;
			for (size_t i = 0; i < ARRAYSIZE(Encodings); ++i)
			{
				if (Encodings[i].BomSize > BufSz)
				{
					BufSz = Encodings[i].BomSize;
				}
			}
			++BufSz;

			char* Buf = new char[BufSz];

			size_t read = state->read(Buf, BufSz);
			if (read > 0)
			{
				for (size_t i = 0; i < ARRAYSIZE(Encodings); ++i)
				{
					// 6 lines of code here are redundant!
					if (Encodings[i].BomSize == 0)
					{
						if (isascii(Buf[0]))
						{
							encoding = Encodings[i].Type;
							break;
						}
					}
					else if (read >= Encodings[i].BomSize && memcmp(Encodings[i].Bom, Buf, Encodings[i].BomSize) == 0)
					{
						encoding = Encodings[i].Type;
						break;
					}
				}
			}
			else
			{
				// For a 0-length file, default to ascii.
				encoding = TextEncoding::TextEncodingAscii;
			}

			delete[] Buf;
			state->set_offset(oldOff);

			if (encoding == TextEncoding::TextEncodingInvalid)
			{
				throw bad_result_exception(MONORET(FACILITY_TEXTMODEL, ERROR_BAD_FORMAT), "Invalid text encoding.");
			}
			return encoding;
		}
	}
}